package search.searchtechniques;


import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jgap.*;
import org.jgap.impl.DefaultConfiguration;
import org.jgap.impl.IntegerGene;
import org.jgap.impl.StockRandomGenerator;

import primitives.cluster.ClusterHead;
import search.fitnessfunctions.util.TreeTranslator;
import search.genes.*;

public class RandomMutationHillClimbingSearch extends Search {

	private ArrayList<Gene> individual;
	private ArrayList<Gene> mutant;
	RandomGenerator rng;
        
        double fitness;
        

	public RandomMutationHillClimbingSearch() {
	}

	public ArrayList<Gene> newIndividual() throws InvalidConfigurationException {
		Configuration gaConf = new DefaultConfiguration();
		
		GeneFactory gf = new GeneFactory(geneType, in.getSize());
		
		ArrayList<Gene> a_individual = new ArrayList<Gene>();
		
		for (int i = 0; i < chromesize; i++) {
			Gene g = gf.getInstance(gaConf);
			g.setToRandomValue(rng);
			a_individual.add(g);
		}
		return a_individual;
	}

	public ArrayList<Gene> copy(ArrayList<Gene> individual) {
		ArrayList<Gene> copy = new ArrayList<Gene>();

		for (int i = 0; i < individual.size(); i++) {
                    Gene ind = individual.get(i);
                    if(ind instanceof InstructionGene){
                        copy.add(((InstructionGene)ind).clone());
                    }else if(ind instanceof StackInstructionGene){
                        copy.add(((StackInstructionGene)ind).clone());
                    }else if(ind instanceof IntegerGene){
                        IntegerGene c = (IntegerGene)ind;
                        IntegerGene n = null;
                        try {
                            n = new IntegerGene(ind.getConfiguration(), c.getLowerBounds(), c.getUpperBounds());
                        } catch (InvalidConfigurationException ex) {
                            Logger.getLogger(RandomMutationHillClimbingSearch.class.getName()).log(Level.SEVERE, null, ex);
                        }
                        n.setAllele(c.intValue());
                        copy.add(n);
                    }else{
                        throw new RuntimeException(String.format("Can't make a clone of gene of type %s object is: %s ",ind.getClass().toString(), ind));
                    }
                    
                   
                }
                

		return copy;

	}

	public ArrayList<Gene> mutateIndividual(ArrayList<Gene> individual) {
		ArrayList<Gene> toMutate = copy(individual);

		int position = rng.nextInt(individual.size() - 1);

		Gene target = toMutate.get(position);
                int sign = 1;
                if(Math.random() >= 0.5) sign = -1;
		//most genes will ignore the position argument (first one), but bitfield needs it to choose which bits to modify.
		target.applyMutation(rng.nextInt(32),sign * rng.nextDouble());

		return toMutate;
	}
	
	private double evaluateIndividual(ArrayList<Gene> individual){
                return ff.evaluateTree(TreeTranslator.getTree(individual, in, geneType));
	}

        
        public void setup() throws InvalidConfigurationException{
            rng = new StockRandomGenerator();
		//todo: allow setting seed of rng;
                
            individual = newIndividual();
            fitness = evaluateIndividual(individual);
            
        }

        public double iteration(){
            
            
            mutant = mutateIndividual(individual);
            double mutant_fitness = evaluateIndividual(mutant);
            
            if (mutant_fitness > fitness) {
                                Logger.getLogger(RandomMutationHillClimbingSearch.class.getName()).log(Level.FINE, "RMHC: got a fitness increase: {0} to {1} (delta {2})",new Object[]{fitness, mutant_fitness,mutant_fitness-fitness});

				fitness = mutant_fitness;
				individual = mutant;
            }
            return fitness;
        }
        
        public ClusterHead getClustering(){
            return TreeTranslator.getTree(individual, in, geneType);
        }
        
        public double getFitness(){
            return fitness;
        }
}
